/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */

package cgl.narada.wsinfra.wsr.impl;


import java.util.Calendar;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.xml.namespace.QName;

import org.apache.xmlbeans.GDuration;

import cgl.narada.wsinfra.WsMessageFlow;
import cgl.narada.wsinfra.WsProcessor;
import cgl.narada.wsinfra.exception.DeploymentException;
import cgl.narada.wsinfra.exception.IncorrectExchangeException;
import cgl.narada.wsinfra.exception.MessageFlowException;
import cgl.narada.wsinfra.exception.ProcessingException;
import cgl.narada.wsinfra.exception.UnknownExchangeException;
import cgl.narada.wsinfra.exception.WsFaultException;
import cgl.narada.wsinfra.schemas.addressing.EndpointReferenceType;
import cgl.narada.wsinfra.schemas.addressing.ToDocument;
import cgl.narada.wsinfra.schemas.soap.EnvelopeDocument;
import cgl.narada.wsinfra.schemas.wsr.RequestDocument;
import cgl.narada.wsinfra.util.UUIDRetrieval;
import cgl.narada.wsinfra.wsa.AddressingHeaders;
import cgl.narada.wsinfra.wsa.WsaFaultRules;
import cgl.narada.wsinfra.wsr.WsrConstants;
import cgl.narada.wsinfra.wsr.WsrElementCreation;
import cgl.narada.wsinfra.wsr.WsrExchangeInfo;
import cgl.narada.wsinfra.wsr.WsrProcessingFactory;
import cgl.narada.wsinfra.wsr.storage.WsrGroupInfoStorageOperations;
import cgl.narada.wsinfra.wsr.storage.WsrProtocolStorageOperations;
import cgl.narada.wsinfra.wsr.storage.WsrStorageException;
import cgl.narada.wsinfra.wsr.storage.WsrStorageService;
import cgl.narada.wsinfra.wsr.storage.impl.WsrGroupTerminationInfoImpl;
import cgl.narada.wsinfra.wsr.storage.impl.WsrStorageWidgetImpl;


/**
 * @author Sima Patel 
 * $Date: 2005/05/04 18:05:34 $
 * $Revision: 1.10 $
 *  This is a utility class which performs several functions that are
    helpful for the processing logic related to the WsrSourceNode class.
*/


public class WsrSourceNodeHelper extends WsProcessor {
    private static final WsrSourceNodeHelper instance = 
                                                      new WsrSourceNodeHelper();
    private WsMessageFlow wsMessageFlow;
    private UUIDRetrieval uuidRetrieval;
    private WsaFaultRules wsaFaultRules;
    private WsrFaultsImpl wsrFaultsImpl;
    private EndpointReferenceType sourceEpr;
    private WsrUtil wsrUtil;
    private WsrElementCreation wsrElementCreation;
    
    private WsrStorageService wsrStorageServiceSourceHelper;
    private WsrGroupInfoStorageOperations wsrGroupInfoStorageOperations;
    private WsrProtocolStorageOperations wsrProtocolStorageOperations;
      
    private String moduleName = "WsrSourceNodeHelper: ";
    
    private WsrSourceNodeHelper() {
        uuidRetrieval = UUIDRetrieval.getInstance();
        wsrFaultsImpl = WsrFaultsImpl.getInstance();
        wsrUtil = WsrUtil.getInstance();
        wsrElementCreation = WsrProcessingFactory.getWsrElementCreation();
    }
    
    public void initialize(WsrStorageService wsrStorageService) 
                throws DeploymentException {
        
        this.wsrStorageServiceSourceHelper = wsrStorageService;
        if (wsrStorageServiceSourceHelper == null) {
            throw new DeploymentException(moduleName + 
                                                    "StorageService is NULL!");
        }
        try {
            wsrProtocolStorageOperations = 
                           wsrStorageServiceSourceHelper.getWsrProtocolStorageOperations();
            wsrGroupInfoStorageOperations = 
                           wsrStorageServiceSourceHelper.getWsrGroupInfoStorageOperations(); 

        } catch (WsrStorageException wsrStorageException) {
            String reason = moduleName + 
                                     "Problems initializing storage services\n"
                                      + wsrStorageException.toString();
            throw new DeploymentException(reason);
        }
    }
    
    /** Sets the message flow which the processor should use */
    public void setMessageFlow(WsMessageFlow wsMessageFlow) 
                throws DeploymentException {
        this.wsMessageFlow = wsMessageFlow;
    }

    /** Gets the message flow which the processor should use. */
    public WsMessageFlow getMessageFlow() {
        return wsMessageFlow;
    }
    
    public static WsrSourceNodeHelper getInstance() {
        return instance;
    }
    public boolean processExchange(EnvelopeDocument envelopeDocument, int direction)
    			throws UnknownExchangeException, IncorrectExchangeException,
					   ProcessingException, MessageFlowException {
    	String errorReport = null;
        
        errorReport = moduleName + "Should NOT have this exchange. This is "
          + "just a helper to the WsrmSourceNode. ";
        
        if (errorReport != null) {
          throw new ProcessingException(errorReport);
        }
        return false;
    }
    
    public void setEndpointReference(EndpointReferenceType endpointReference) {
    	this.sourceEpr = endpointReference;
    }
    
    public void checkExchangeType(WsrExchangeInfo wsrExchangeInfo, 
				  				  int direction)
                throws UnknownExchangeException, 
                       IncorrectExchangeException {

        if (direction == WsMessageFlow.FROM_NETWORK) {
            checkExchangeFromNetwork(wsrExchangeInfo);
        }

        if (direction == WsMessageFlow.FROM_APPLICATION) {
            checkExchangeFromApplication(wsrExchangeInfo);
        }    
    }
  
    /** Only  CreateRequest Exchange is valid. */
    private void checkExchangeFromApplication(WsrExchangeInfo wsrExchangeInfo)
                 throws UnknownExchangeException, IncorrectExchangeException {
    
        String from = "Application. ";
        String problemElement = "";
        if (wsrExchangeInfo.hasRequest() ) {
            problemElement += " Request ";
        }
        if (wsrExchangeInfo.hasPollRequest()) {
            problemElement += " PollRequest ";
        }
        if (wsrExchangeInfo.hasResponse() ) {
            problemElement += " Response ";
        }
        if(wsrExchangeInfo.isProcessPollRequest()){
            problemElement += " ProcessPollRequestAction";
        }
        if(wsrExchangeInfo.isProcessResponse()){
            problemElement += " ProcessResponseAction";
        }
        if (!problemElement.equals("")) {
            throwIncorrectExchangeException(problemElement, from);
        }

    }
  

    /** ONLY Process Response Exchange is valid*/
    private void checkExchangeFromNetwork(WsrExchangeInfo wsrExchangeInfo)
                 throws UnknownExchangeException, IncorrectExchangeException {
    
        String from = "Network. ";
        String problemElement = "";
        if (wsrExchangeInfo.hasRequest() && wsrExchangeInfo.hasPollRequest()){
            problemElement += "Request and PollRequest";
        }
        if (!problemElement.equals("")) {
            throwIncorrectExchangeException(problemElement, from);
        }  
        return;
    }
  
    /** This method enforces the rule that the [wsa:To] element should be
    present within a SOAP header element. If this is not present an
    exception needs to be thrown. */
    public void checkForProblemsInToDocument(AddressingHeaders addressingHeaders)
                throws WsFaultException {
        
        ToDocument toDocument = addressingHeaders.getTo();
        String errorReport = "Problems with the [wsa:To] element. ";
        boolean problems = false;
        if (toDocument == null) {
            errorReport += ("This CANNOT be null. It has to be specified!");
            problems = true;
        }
        if (!problems) {
            String to = toDocument.getTo().getStringValue();
            errorReport += ("The value of the <To> element cannot be NULL.");
        }    
        if (problems) {
            throwInvalidMessageFaultException(errorReport, addressingHeaders);
        }
    }
    /** Throws an Invalid MessageFault exception based on the specified
    parameters */
    public void throwInvalidMessageFaultException(String reason, 
				            AddressingHeaders addressingHeaders) 
                throws WsFaultException {
        
        EndpointReferenceType faultTo = 
                                   wsaFaultRules.sendFaultTo(addressingHeaders);

        WsFaultException wsFaultException = new WsFaultException
                                           (wsrFaultsImpl.getSoapSenderQName(),
                                                                        reason);
        wsFaultException.setFaultTo(faultTo);
        throw wsFaultException;
    }
    private void throwIncorrectExchangeException(String element, String from)
                 throws IncorrectExchangeException {
        String reason =  moduleName + "Should NOT have received exchange " +
                "with element [" + element + "] from the " + from;
        throw new IncorrectExchangeException(reason);
    }
    /** Generates New GroupId for Destination */
    public String createGroupId(){
      
        String groupId = uuidRetrieval.getRandomBasedUUIDAsString();
        return groupId;
    }
    /** Create Request from specified parameters */
    public RequestDocument createRequest(String groupId, 
                                         Calendar groupExpiryTime,
                                         GDuration groupMaxIdleDuration,
                                         long sequenceNumber, 
                                         boolean isLastMessage, 
                                         Calendar messageExpiryTime,
                                         String replyPattern, String replyTo){
        
        RequestDocument requestDocument = null;
        if(groupExpiryTime == null && groupMaxIdleDuration == null){
            if(replyPattern.trim().equalsIgnoreCase("CALLBACK")){
                requestDocument = wsrElementCreation.newRequest(groupId, 
                                                              sequenceNumber,
                                                              isLastMessage,
                                                              messageExpiryTime,
                                                              replyPattern, 
                                                              replyTo);   
            }else{
                requestDocument = wsrElementCreation.newRequest(groupId, 
                                                              sequenceNumber,
                                                              isLastMessage,
                                                              messageExpiryTime,
                                                              replyPattern);
            }
        }
        if((groupExpiryTime != null && groupMaxIdleDuration == null)||
                     (groupExpiryTime != null && groupMaxIdleDuration != null)){
            if(replyPattern.trim().equalsIgnoreCase("CALLBACK")){
                requestDocument = wsrElementCreation.newRequest(groupId, 
                                                              groupExpiryTime,
                                                              sequenceNumber,
                                                              isLastMessage,
                                                              messageExpiryTime,
                                                              replyPattern, 
                                                              replyTo);
            }else{
                requestDocument = wsrElementCreation.newRequest(groupId, 
                                                              groupExpiryTime, 
                                                              sequenceNumber, 
                                                              isLastMessage,
                                                              messageExpiryTime,
                                                              replyPattern);

            }
        }

        if(groupExpiryTime == null && groupMaxIdleDuration != null){
            if(replyPattern.trim().equals("CALLBACK")){
                requestDocument = wsrElementCreation.newRequest(groupId, 
                                                           groupMaxIdleDuration,
                                                           sequenceNumber,
                                                           isLastMessage,
                                                           messageExpiryTime,
                                                           replyPattern, 
                                                           replyTo);

            }else{
                requestDocument = wsrElementCreation.newRequest(groupId, 
                                                           groupMaxIdleDuration, 
                                                           sequenceNumber, 
                                                           isLastMessage,
                                                           messageExpiryTime, 
                                                           replyPattern);
            }

        }
        return requestDocument;
    }
  
    /** process the group acknowledgements with single message
    (Process Non Sequence Reply Acknowledgements*/
    public void processNonSequenceReplyAcknowledgement(
                                  Vector nonSequenceReplyAcknowledgementVector)
                throws WsrStorageException{
        
        if(nonSequenceReplyAcknowledgementVector == null){
            return;
        }
        else{
            for(int count = 0;count<nonSequenceReplyAcknowledgementVector.size()
                                                                     ;count++){
                String groupId = nonSequenceReplyAcknowledgementVector.
                                                    elementAt(count).toString();
                if(!processAcknowledgements(groupId, new long[]{0})){
                    continue;
                }
            }

        }
  }
    
  /** process the Sequence Reply Acknowledgements*/
  public void processSequenceReplyAcknowledgement(Hashtable table)
              throws WsrStorageException{
      if(table == null){
          return;
      }else{
          Enumeration keys = table.keys();
          while(keys.hasMoreElements()){
              String groupId = keys.nextElement().toString();
              Vector acknowledgements =(Vector)(table.get(groupId));
              long[] acks = createLongArrayFromVector(acknowledgements);
              if(!processAcknowledgements(groupId,acks)){
                  continue;
              }
          }
      }
  }
  /** process Acknowledgements */
  public boolean processAcknowledgements(String groupId, long[] acks)
                 throws WsrStorageException{
      
      if(!wsrGroupInfoStorageOperations.isGroupExists(groupId)){
          return false;
      }
      wsrProtocolStorageOperations.processAcknowledgementsOnGroup(groupId,
                                                                          acks);
      return true;
  }
  
  
  /** process Non Sequence Reply Message Processing Failure Fault
  (It is nothing but it will retransmit the message for the given group*/
  public void processNonSequenceReplyMessageProcessingFailureFault(
                                                    Vector retransmissionVector)
              throws WsrStorageException, MessageFlowException{
      
      if(retransmissionVector == null){
          return;
      }else{
          for(int i=0; i<retransmissionVector.size(); i++){
              String groupId = retransmissionVector.elementAt(i).toString();
              processMessageProcessingFailureFault(groupId, new long[]{0});
           
          }
      }
  }
  /** process Sequence Reply Message Processing Failure Fault
  (It is nothing but it will retransmit the messages for the given group*/
  public void processSequenceReplyMessageProcessingFailureFault(
                         Hashtable retransmissionTable)
              throws WsrStorageException, MessageFlowException{
      
      if(retransmissionTable == null){
          return;
      }else{
          Enumeration keys = retransmissionTable.keys();
          while(keys.hasMoreElements()){
              String groupId = keys.nextElement().toString();
              Vector retVector =(Vector)(retransmissionTable.get(groupId));
              long[] seqNum = createLongArrayFromVector(retVector);
              processMessageProcessingFailureFault(groupId,seqNum);
          }
      }
       
  }
   /** process Message Processing Failure Fault*/
   public void processMessageProcessingFailureFault(String groupId,long[]seqNum)
               throws WsrStorageException, MessageFlowException{
   	   WsrGroupTerminationInfoImpl wsrGroupTerminatinInfoImpl=
	   		wsrProtocolStorageOperations.getGroupTerminationInfo(groupId);
       WsrStorageWidgetImpl[] wsrStorageWidgetsImpl =
                wsrProtocolStorageOperations.getStoredElements(groupId, seqNum);
       for(int index =0;  index< wsrStorageWidgetsImpl.length; index++){    
       	   Calendar messageExpiryTime = 
       	   	               wsrStorageWidgetsImpl[index].getMessageExpiryTime();
       	   if(wsrUtil.isExpiryTimePassed(messageExpiryTime)){
       	       wsrGroupTerminatinInfoImpl.setMessageExpired(true);
       	   }else {
       	   	byte numberofretries = wsrStorageWidgetsImpl[index].getNumberOfRetries();
       	   	   if( numberofretries < 3){
       	   	   		if(wsrStorageWidgetsImpl[index].getRetransmissionTime() 
       	   	   				<= System.currentTimeMillis()){
         	           EnvelopeDocument envelopeDocument = 
  		                           wsrStorageWidgetsImpl[index].getEnvelope(); 
         	           wsrProtocolStorageOperations.resetRetransmissionTime(
  		                                         wsrStorageWidgetsImpl[index],
                                             System.currentTimeMillis()+300000);
         	           wsrProtocolStorageOperations.resetNumberOfRetries(
         	           		                     wsrStorageWidgetsImpl[index],
										(byte)(numberofretries+1));
         	           enrouteToNetwork(envelopeDocument);
  		           
       	   	   		}
       	   	   	
       	   	   }else{
       	   	   		wsrStorageWidgetsImpl[index].setMessageStatus(
       	   	   		        WsrConstants.MESSAGE_FAULTED);
       	   	   		wsrStorageWidgetsImpl[index].setMessageFault(
       	   	   				wsrFaultsImpl.getPermenentProcessingFailure());
       	   	   		wsrProtocolStorageOperations.store(wsrStorageWidgetsImpl[index]);
       	   	   }
       	       
           }
       }
   }
   /** process Non sequence Reply Faults other than Message Processing Failure
   Fault */
   public void processNonSequenceReplyFault(Hashtable nonSequenceReplyFault)
               throws WsrStorageException{
       if(nonSequenceReplyFault == null){
           return;
       }else{
           Enumeration keys = nonSequenceReplyFault.keys(); 
           while(keys.hasMoreElements()){
               String groupId = keys.nextElement().toString();
               QName fault =(QName)(nonSequenceReplyFault.get(groupId));
               if(!processFault(groupId, 0, fault)){
                   continue;
               }       
           }     
       }
   }
   /** process sequence Reply Faults other than Message Processing Failure
   Fault */
   public void processSequenceReplyFault(Hashtable sequenceReplyFault)
               throws WsrStorageException{
   	   
       if(sequenceReplyFault == null){
	   return;
       }else{
           Enumeration keys = sequenceReplyFault.keys(); 
           while(keys.hasMoreElements()){
               String groupId = keys.nextElement().toString();
               Hashtable seqNumAndFaultTable = 
                                   (Hashtable)(sequenceReplyFault.get(groupId));
               Enumeration seqNumkeys = seqNumAndFaultTable.keys(); 
               while(seqNumkeys.hasMoreElements()){
                   Long seqNumObj = ((Long)seqNumkeys.nextElement());
                   long seqNum = seqNumObj.longValue();
                   QName fault = ((QName)(seqNumAndFaultTable.get(seqNumObj)));
                   if(!processFault(groupId, seqNum, fault)){
                       break;
                   }    
               }
           }
       }
   }
   /** Process Faults other than messageProcessingFailure fault*/
   public boolean processFault(String groupId, long sequenceNumber, QName fault)
                  throws WsrStorageException{
   	
       if(!wsrGroupInfoStorageOperations.isGroupExists(groupId)){
          return false;
       }
       wsrProtocolStorageOperations.processFaultOnGroup(
                                                 groupId,sequenceNumber,fault);
       if(fault.toString().equalsIgnoreCase(
       		                      wsrFaultsImpl.getGroupAborted().toString())){
       
       	WsrGroupTerminationInfoImpl wsrGroupTerminationInfoImpl1 = 
       		wsrProtocolStorageOperations.getGroupTerminationInfo(groupId);
       	wsrGroupTerminationInfoImpl1.setGroupClosed(true); 
       	wsrProtocolStorageOperations.storeGroupTerminationInfo(
       	        wsrGroupTerminationInfoImpl1);
       }
       return true;
   }
     
   /** create Long Number array from Vector */
   public long[] createLongArrayFromVector(Vector vector){
       long[] numbers = new long[vector.size()];
       for(int index = 0;index < vector.size();index++){
           numbers[index]=((Long)(vector.elementAt(index))).longValue();
       }
       return numbers;
   }
   
   /** Throws an UnknownGroup Fault exception based on the specified
   parameters */
   public void throwUnknownGroupFaultException(
                                            AddressingHeaders addressingHeaders,
				            String groupId) 
               throws WsFaultException {
       EndpointReferenceType faultTo = wsaFaultRules.sendFaultTo(
                                                             addressingHeaders);
    
       WsFaultException wsFaultException = new WsFaultException(
                                        wsrFaultsImpl.getSoapSenderQName(),
			                wsrFaultsImpl.getUnknownGroupReason() );
       wsFaultException.setFaultTo(faultTo);
       throw wsFaultException;
   }
  
}
